package no.rmz.chez.chess.pieces;

import java.util.Collection;

import no.rmz.chez.chess.board.ChessBoard;
import no.rmz.chez.chess.board.Color;
import no.rmz.chez.chess.board.Coord;
import no.rmz.chez.chess.movement.Direction;
import no.rmz.chez.game.Move;

/**
 * A class for describing chesspieces and accessing the
 * possible chesspieces as static members.  Chess pieces
 * should not be created by user programs instead the
 * static instances made public by this class should be
 * used (e.g. BP for black pawn). This simplifies
 * table lookups, since we then only have to check for 
 * object equality, not for equality of components
 * within an object.
 *
 * So far I've been using my own particular notation,
 * that is something I should stop doing, instead I should
 * adopt the stadndardized notation http://en.wikipedia.org/wiki/Algebraic_Chess_Notation
 * Probably the long format.
 *
 * http://en.wikipedia.org/wiki/Chess_symbols_in_Unicode has
 * a list of chess symbols in unicode. Can they be used?
 *
 */
abstract public class ChessPiece implements AlgebraicChessNotationElement {

    /**
     * Black Pawn.
     */
    public final static ChessPiece BP = new Pawn        (Color.BLACK);
   
    /**
     * Black rook.
     */
    public final static ChessPiece BR = new Rook        (Color.BLACK);
    
    /**
     * Black knight.
     */
    public final static ChessPiece BN = new Knight      (Color.BLACK);
    
    /**
     * Black bishop.
     */
    public final static ChessPiece BB = new Bishop      (Color.BLACK);
    
    /**
     * Black king
     */
    public final static ChessPiece BK = new King        (Color.BLACK);
    
    
    /**
     * Black queen.
     */
    public final static ChessPiece BQ = new Queen       (Color.BLACK);
    
    /**
     * White pawn.
     */
    public final static ChessPiece WP = new Pawn        (Color.WHITE);
    
    /**
     *  White rook.
     */
    public final static ChessPiece WR = new Rook        (Color.WHITE);
   
    /**
     * White knight.
     */
    public final static ChessPiece WN = new Knight      (Color.WHITE);
   
    /**
     * White bishop.
     */
    public final static ChessPiece WB = new Bishop      (Color.WHITE);
    
    /**
     * White king.
     */
    public final static ChessPiece WK = new King        (Color.WHITE);
    
    /**
     * White queen.
     * 
     */
    public final static ChessPiece WQ = new Queen       (Color.WHITE);

    /**
     * Is this piece a king? (by default it isn�t
     * is overridden by the king).
     * @return
     */
    public boolean isKing() {
        return false;
    }

    /**
     * The color of the piece, either black or white.
     */
    private Color  color;

    /**
     * A long name, like "black rook" or something similar.
     */
    private String longName;

    /**
     * A short name, e.g. "p" for a white pawn
     * and "P" for a black pawn.
     */
    private String shortName;
    
    /**
     * A string representation used to print the piece.
     */
    private String stringRepresentation = null;
    
    /**
     * A lock for the string representation.
     */
    private final Object stringRepLock = new Object();

    /**
     * Get a name used for printing the piece,
     * by default the short name, but could be anything.
     * @return a string describing the piece
     */
    public final String getPrintName() {
        synchronized (stringRepLock) {
            if (stringRepresentation == null) {
                String result = new String(shortName);
                if (getColor() == Color.WHITE) {
                    result = result.toLowerCase();
                } else {
                    result = result.toUpperCase();
                }
                stringRepresentation = result;
            }
        }
        return stringRepresentation;
    }

    /**
     * Return the Algebraic Chesss Notation (ACN) representation
     * of the piece.
     */
    @Override
    public final String getACNRepresentation() {
        final String result = getPrintName();
        if (result.equalsIgnoreCase("p")) {
            return "";
        } else {
            return result;
        }
    }

    /**
     * Accessor for the color of the piece.
     */
    public Color getColor() {
        return color;
    }

    /**
     * Create a new chesspiece.
     * 
     * @param color
     * @param longName
     * @param shortName
     */
    protected  ChessPiece(Color color, String longName, String shortName) {
        super();
        this.color     = color;
        this.longName  = longName;
        this.shortName = shortName;
    }

    /**
     * The maximum length a capture ray can be cast when
     * evaluating the next move.
     */
    private final static int MAX_RAY_LENGTH = 7;
    
   

    final public void addNEWSRays (Collection<Move> result, ChessBoard b, Coord c) {
        addNEWSRays (result,  b,  c, MAX_RAY_LENGTH);
    }

    final public void addDiagonalRays (Collection<Move> result, ChessBoard b, Coord c) {
        addDiagonalRays (result,  b,  c, MAX_RAY_LENGTH);
    }
    
    final public void addRaysAllDirections (Collection<Move> result, ChessBoard b, Coord c){
        addRaysAllDirections(result, b, c, MAX_RAY_LENGTH);
    }
    

    final public void addNEWSRays (Collection<Move> result, ChessBoard b, Coord c, int maxlen){
        addRays(result, b, c, maxlen, Direction.getNEWS());
    }

   
    
    final public void addRaysAllDirections (Collection<Move> result, ChessBoard b, Coord c, int maxlen){
        addRays(result, b, c, maxlen, Direction.getAllDirections());
    }
    


    final public void addDiagonalRays (Collection<Move> result, ChessBoard b, Coord c, int maxlen){
        addRays(result, b, c, maxlen, Direction.getDiagonals());
    }

    /**
     * True iff the parameter piece is one that can be captured by
     * this piece.
     * @param p
     * @return
     */
    public final boolean captureIsAllowed(ChessPiece p){
        // Can capture all pieces with different color.
        // XXX Can pawns capture kings?
        
        return (getColor()  != p.getColor());
    }
    
    
    final public void addMovesToLegaDestinations(
            Collection<Move> result, 
            ChessBoard b, 
            Coord sourceLocation,
            Collection<Coord> possibleDestinations) {
        for (Coord destination: possibleDestinations) {
            addMoveIfLegal(result, b, sourceLocation, destination);
        }
    }
    
    
    /**
     * If a move is legal, perform it. Return true iff
     * no more moves can be done in a ray containing the target.
     * 
     * @param result
     * @param b
     * @param source
     * @param target
     * @return
     */
    final static public boolean addMoveIfLegal(
            final Collection<Move> result, 
            final ChessBoard       b, 
            final Coord            source, 
            final Coord            target){
        boolean blocked = false;
        if (target != null ) {
            final ChessPiece sourcePiece = b.getPieceAt(source);
            final ChessPiece targetPiece = b.getPieceAt(target);
            if (targetPiece != null ) {
                blocked = true;
                if (sourcePiece.captureIsAllowed(targetPiece)) {
                    Move m = new Move(b.getPieceAt(source), source, target);
                    m.setCapturing(true);
                    result.add(m);
                }
            } else {
                result.add(new Move(b.getPieceAt(source), source, target));
            }
        }
        return blocked;
    }

    /**
     * Compute capture rays from a position on a board.
     * @param result
     * @param b
     * @param origin
     * @param maxlen
     * @param directions
     */
     private static void addRays(
            final Collection<Move>      result,
            final ChessBoard            b,
            final Coord                 origin,
            final int                   maxlen,
            final Collection<Direction> directions){
        for (Direction direction: directions) {
            addRay(result, b, origin, maxlen, direction);
        }
    }

    /**
     * Compute capture rays from a set of positions on a board.
     * @param result
     * @param b
     * @param source
     * @param maxlen
     * @param direction
     */
    public static final  void addRay(
            final Collection<Move>    result,
            final ChessBoard          b,
            final Coord               source,
            final int                 maxlen,
            final Direction           direction) {
        Coord   target = source;
        boolean blocked = false;
        int     len     = 1;
        do {
            target = target.stepInDirection(direction);
            blocked = addMoveIfLegal(result, b, source, target);
            len    += 1;
        } while (  !blocked
                && (target != null)
                && (len < maxlen));
    }
    
    /**
     * To the collection of moves present, add all the
     * moves this piece is able to add.
     * 
     * This method is intended to be overridden by subclasses,
     * but the superclass definition does exist, but just
     * performs sanity checks.
     * 
     * @param result  the collection to add all moves to 
     * @param b       The chessboard we're moving on
     * @param c       The coordinate of the present piece.
     */
      public void addMoves(
            final Collection<Move> result,
            final ChessBoard b,
            final Coord source) {
        assert (getColor() == Color.WHITE || getColor() == Color.BLACK);
    }

    public String getLongName() {
        return longName;
    }

    @Override
    public String toString() {
        return "ChessPiece{" + "stringRepresentation=" + stringRepresentation + '}';
    }
}
